//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <ddk.h>
#include <drvpub.h>
#include <bulverde.h>
#include "display_def.h"

class CDisplay : public CDeviceDriver
{
public:
    CARAPI Read(
                    /* [in] */ Int64 u64Offset,
                    /* [in] */ Int32 bytesToRead,
                    /* [out] */ MemoryBuf * pBuffer,
                    /* [out] */ IEvent * * ppCompletionEvent);
    CARAPI Write(
                    /* [in] */ Int64 u64Offset,
                    /* [in] */ const MemoryBuf &buffer,
                    /* [out] */ Int32 * pBytesWritten,
                    /* [out] */ IEvent * * ppCompletionEvent);
    CARAPI Control(
                    /* [in] */ Handle32 nControlCode,
                    /* [in] */ const MemoryBuf &inBuffer,
                    /* [out] */ MemoryBuf * pOutBuffer,
                    /* [out] */ IEvent * * ppCompletionEvent);
    virtual void Dispose();

public :
    ECode  CDisplay::GetOverlayOut(
                    /* [in] */ int layer,
                    /* [out] */ MemoryBuf * pOutData);

    DisplayContext m_disCxt;
    Address m_userFrameBufAddr[3];
    UInt32    m_userFrameBufSize[3];
    DzMutex   m_lock;
};
static CDisplay s_display;

ECode CDisplay::Read(
                /* [in] */ Int64 u64Offset,
                /* [in] */ Int32 bytesToRead,
                /* [out] */ MemoryBuf * pBuffer,
                /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode CDisplay::Write(
                /* [in] */ Int64 u64Offset,
                /* [in] */ const MemoryBuf &buffer,
                /* [out] */ Int32 * pBytesWritten,
                /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode  CDisplay::GetOverlayOut(
                /* [in] */ int layer,
                /* [out] */ MemoryBuf * pOutData)
{
    OverlayFormat *pOutOverlay;
    OverlayFormat *pDrvOverlay;
    Address *pShareBufAddr;
    UInt32    *pShareBufSize;
    Address ShareAddr, PhysAddr;
    int nMapSize;
    ECode ec;

    if (pOutData == NULL) {
        return E_INVALID_ARGUMENT;
    }
    if (pOutData->GetCapacity() < (int)sizeof(OverlayFormat)) {
        return E_OUT_OF_MEMORY;
    }

    pDrvOverlay = &m_disCxt.overlay[layer];
    if (layer == 1) {
        nMapSize =  ROUNDUP_PAGE(m_disCxt.uChan1Size);
        ;
        PhysAddr = m_disCxt.paOverlay1;
    }
    else if (layer == 2) {
        nMapSize = OVERLAY2TOTAL_FB_MAXSIZE;
        PhysAddr = m_disCxt.paOverlay2Y;
    }
    else
        return E_INVALID_ARGUMENT;

    pShareBufAddr = &m_userFrameBufAddr[layer];
    pShareBufSize = &m_userFrameBufSize[layer];
    pOutOverlay = (OverlayFormat *)pOutData->GetPayload();

    if(pDrvOverlay->Enable == FALSE) return E_ACCESS_DENIED;
    memcpy(pOutOverlay, pDrvOverlay, sizeof(OverlayFormat));

    // Map the virtual address that user application can use
    ec = DzIoRemap( 0, nMapSize, PhysAddr,
                  0, MemoryProtection_RW | IoRemapFlag_BufferedWrite, &ShareAddr);
    if (FAILED(ec))  return ec;

    pOutOverlay->pChannelFb[0] = (void*)ShareAddr;

    if (layer == 2) {
        pOutOverlay->pChannelFb[1] = (void *)(ShareAddr + OVERLAY2Y_FB_MAXSIZE);
        pOutOverlay->pChannelFb[2] = (void *)((Address)pOutOverlay->pChannelFb[1] +
                                  OVERLAY2Cb_FB_MAXSIZE);
    }

    *pShareBufAddr = ShareAddr;
    *pShareBufSize = nMapSize;
    pOutData->SetUsed(sizeof(OverlayFormat));

    return NOERROR;
}

extern unsigned long lastwake;
unsigned long displaystamp = 0;
void SetBrightnessOn();
void SetBrightnessLow();
void SetBrightnessVeryLow();
void PoChangeLcdModeOn();
EXTERN_C Boolean IsOverlayEnable();
void DoSpConfig(void *pArg);
ECode CDisplay::Control(
                /* [in] */ Handle32 nControlCode,
                /* [in] */ const MemoryBuf &inBuffer,
                /* [out] */ MemoryBuf * pOutBuffer,
                /* [out] */ IEvent * * ppCompletionEvent)
{
    char *tBuf = NULL;
    ECode ec = NOERROR;

    //cprintf("Display::Control %d\n", nControlCode);
    DBGOUT(DBG_CTLCODE, ("Display::Control %d\n", nControlCode));
    //kprintf("+++++++++Display::Control %d\n", nControlCode);

    WaitResult wr;
    m_lock.Lock(&wr);

    int layer;
    switch (nControlCode) {
        case 0 :
            //cls
            memset((void *)m_disCxt.vaBasePlane, 0, BASEPLANE_FB_SIZE);
            break;

        case 1 ://get frame buffer physical address
        /* LCD   PhyscalBase | width | height | bitspp
        BITS64 ......   32  ... 20  ...   8  ...   0
        */
            if (pOutBuffer == NULL) {
                return E_INVALID_ARGUMENT;
            }
            assert(NULL != pOutBuffer->GetPayload());
            tBuf = (char*)pOutBuffer->GetPayload();
            *(UInt32*)tBuf = (( LCD_WIDTH & 0xfff ) << 20) |
                           ((LCD_HEIGHT & 0xfff) << 8) | (BASEPLANE_PIXELBITS & 0xff);

            ((UInt32*)tBuf)[1] = (UInt32)m_disCxt.paBasePlane;
            pOutBuffer->SetUsed(sizeof(UInt32));
            break;

        case 3 :{ //get frame buffer virtual address
            Address userLcdBaseAddr;

            if (pOutBuffer == NULL) {
                return E_INVALID_ARGUMENT;
            }
            assert(NULL != pOutBuffer->GetPayload());
            tBuf = (char*)pOutBuffer->GetPayload();

            // Init display hardware
            ec = Display_Init(&m_disCxt);
            if (FAILED(ec)) return ec;
//
//            ec = DzIoRemap( 0, BASEPLANE_FB_SIZE, m_disCxt.paBasePlane,
//                          0, MemoryProtection_RWVM, &userLcdBaseAddr);
//            if (FAILED(ec)) return ec;

            userLcdBaseAddr = (Address)DzKIoRemap(m_disCxt.paBasePlane,
                                BASEPLANE_FB_SIZE, IoRemapFlag_BufferedWrite);

            ((UInt32*)tBuf)[0] = (UInt32)userLcdBaseAddr;
            pOutBuffer->SetUsed(sizeof(UInt32));
            m_userFrameBufAddr[0] = userLcdBaseAddr;
            m_userFrameBufSize[0] = BASEPLANE_FB_SIZE;

            NotifyByIsr(EVENT_POWER,(int)DoSpConfig,(void *)0);

            break;
        }

        case 7:{
            // Get baseplane format
            FrameBufferFormat fbFormat;

            if (pOutBuffer == NULL) {
                return E_INVALID_ARGUMENT;
            }
            assert(NULL != pOutBuffer->GetPayload());

            fbFormat.Color = BASEPLANE_COLORFORMAT;
            fbFormat.Bpp = BASEPLANE_PIXELBITS;
            fbFormat.Width = LCD_WIDTH;
            fbFormat.Height = LCD_HEIGHT;
            pOutBuffer->Copy((Byte *)&fbFormat, sizeof(FrameBufferFormat));
            break;
        }

        case 8:
            // Set overlay, if success also return the current format
            PoChangeLcdModeOn();//resume the lcd first
            OverlayFormat *pOverlayFormat;

            assert(NULL != inBuffer.GetPayload());

            pOverlayFormat = (OverlayFormat *)inBuffer.GetPayload();

            ec = Display_SetOverlay(&m_disCxt, pOverlayFormat);

            if (NOERROR == ec) {
                ec = GetOverlayOut(pOverlayFormat->Layer, pOutBuffer);
            }
            break;

        case 9:
            if (inBuffer.GetPayload() == NULL)
                layer = 2; // default: overlay2
            else
                layer = *(int *)inBuffer.GetPayload();
            // Get overlay
            ec = GetOverlayOut(layer, pOutBuffer);

            break;

        case 10:{
            // Disable overlay
            if (pOutBuffer == NULL) {
                return E_INVALID_ARGUMENT;
            }
            int *pBaseFmt = (int *)pOutBuffer->GetPayload();
            if (inBuffer.GetPayload() == NULL)
                layer = 2; // default: overlay2
            else
                layer = (int)*(UInt16 *)inBuffer.GetPayload();
            Display_DisableOverlay(&m_disCxt, layer);
            if (pBaseFmt) {
                *pBaseFmt = m_disCxt.basePlaneFormat;
                pOutBuffer->SetUsed(sizeof(int));
            }
            Address userFrameBufAddr = m_userFrameBufAddr[layer];
            if (userFrameBufAddr) {
                DzIoUnmap(userFrameBufAddr, m_userFrameBufSize[layer]);
                m_userFrameBufAddr[layer] = NULL;
            }
            break;
        }

        case 111:
            // Turn on the backlight.
            BackLight_Open(&m_disCxt);

            break;

        case 112:
            // Turn off the backlight.
            BackLight_Close(&m_disCxt);
            break;

        case 13:
            // Set brightness 0~10
            if (inBuffer.GetPayload() == NULL)
                layer = 5; // default brightness, Normal
            else
                layer = *(int *)inBuffer.GetPayload();

            BackLight_SetBrightness(&m_disCxt, layer);

            break;

        case 14:{
            Byte transLvl;

            if (inBuffer.GetPayload() == NULL)
                transLvl = 255; // default
            else
                transLvl = *inBuffer.GetPayload();

            LcdCtrl_SetTransLevel(&m_disCxt, transLvl);

            break;
        }

        //Power Suspend
        case 1000:// 0x0 Suspend Graceful
                  // 0x1 Suspend Immediate
        {
            // Keep LCD light on when using overlay2.
            // Overlay1 isn't supported now.
            if (IsOverlayEnable()) {
                //cprintf("Error: power management disabled when set overlay2.\n");
                ec = E_ACCESS_DENIED;
                break;
            }

            int type;
            if (inBuffer.GetPayload() == NULL) {
                type = 0x0;//default 0x0 Suspend Graceful
            }
            else {
                type = *(int *)inBuffer.GetPayload();
            }
            BackLight_Close(&m_disCxt);
            Display_Suspend(&m_disCxt, type, 0);
        }
        break;

        //Power Resume
        case 1001:

            Display_Resume(&m_disCxt, 0);
            BackLight_Open(&m_disCxt);
            SetBrightnessOn();
            break;

        case 1002://Power Wakeup
//            Display_Resume(&m_disCxt, 1);//Move to Display_Wake()
//            cprintf("Display on. used %d, Delay %d\n", (OSCR0 - lastwake) * 3, (OSCR0 - displaystamp) * 3);

            BackLight_Open(&m_disCxt);
            SetBrightnessOn();
            break;

        case 1010://On2Low
            // Keep LCD light on when using overlay2.
            // Overlay1 isn't supported now.
            if (IsOverlayEnable()) {
                //cprintf("Error: power management disabled when set overlay2.\n");
                ec = E_ACCESS_DENIED;
                break;
            }

            SetBrightnessLow();
            break;

        case 1011://Low2On

            SetBrightnessOn();
            break;

        default:
            ec = E_NOT_IMPLEMENTED;
            break;
    }

    m_lock.Unlock();

    return ec;
}

void CDisplay::Dispose()
{
}

DisplayContext *g_pdisCxt;

EXTERN IDeviceDriver * CDECL CreateDisplay(uint_t uDeviceNo, void *pvParameter)
{
    ECode ec;

    // Create display object
    ec = Display_Create(&s_display.m_disCxt);
    if (FAILED(ec)) {
        cprintf("Create display error!\n");
        return NULL;
    }

    g_pdisCxt = &s_display.m_disCxt;

    cprintf("Initialize display OK!\n");

    //Initialize PWM
    PWMCR0 = 0;
    PWMDCR0 = 0x100;
    PWMPCR0 = 0x3FF;

    //cls

    return &s_display;
}

EXTERN_C OverlayFormat *GetDisplayOverlayFormat(int layer)
{
    if(layer != 1 && layer != 2)return NULL;
    return &g_pdisCxt->overlay[layer];
}

EXTERN_C Boolean IsOverlayEnable()
{
    return (g_pdisCxt->overlay[1].Enable || g_pdisCxt->overlay[2].Enable);
}

EXTERN_C void Display_Wake()
{
    //cputs("Display wake ... \n");
    displaystamp = OSCR0;
    WaitResult wr;
    s_display.m_lock.Lock(&wr);
    Display_Resume(g_pdisCxt,1);
    s_display.m_lock.Unlock();
    //cprintf("Display wake OK. used %d\n", (OSCR0 - displaystamp) * 3);
    displaystamp = OSCR0;
}

// For camera
EXTERN_C Boolean Display_ChangeOverlay2Format(Boolean bRecover, OverlayFormat *pTempFormat)
{
    static OverlayFormat s_backOverlay2;
    OverlayFormat *pNewOverlay2;

    if (!g_pdisCxt->overlay[2].Enable) return FALSE;

    if (bRecover) {
        pNewOverlay2 = &s_backOverlay2;
    }
    else {
        pNewOverlay2 = pTempFormat;
        memcpy(&s_backOverlay2, &g_pdisCxt->overlay[2], sizeof(OverlayFormat));
    }

    Display_DisableOverlay(g_pdisCxt, 2);

    if (NOERROR == Display_SetOverlay(g_pdisCxt, pNewOverlay2)) {

        return TRUE;
    }
    else
        return FALSE;
}
